@itpay/cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/bin/itp +1 -1
- package/docs/agent/buyer/cart-checkout.json +1 -1
- package/docs/agent/buyer/payment-qr.json +6 -3
- package/docs/agent/buyer/payment-wait.json +3 -1
- package/docs/agent/buyer/quickstart.json +5 -4
- package/lib/account-status.js +157 -0
- package/lib/buyer.js +80 -7
- package/lib/render-human.js +69 -33
- package/lib/runtime.js +8 -84
- package/package.json +1 -1
- package/skills/itpay-buyer/SKILL.md +9 -6
package/README.md
CHANGED
|
@@ -158,6 +158,19 @@ Before starting a new purchase, agents should inspect recoverable local state:
|
|
|
158
158
|
itp status --refresh --json
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
+
Humans can use the default account overview:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
itp status
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
Account: buyer_7xK2mP9vQ4
|
|
169
|
+
Linked: alipay, wechat
|
|
170
|
+
Orders: 12
|
|
171
|
+
Device: Codex on MacBook-Pro (active)
|
|
172
|
+
```
|
|
173
|
+
|
|
161
174
|
If an unfinished run exists, continue it:
|
|
162
175
|
|
|
163
176
|
```bash
|
|
@@ -208,7 +221,7 @@ itp buyer checkout status <checkout_id> --json
|
|
|
208
221
|
For the one-command buyer helper:
|
|
209
222
|
|
|
210
223
|
```bash
|
|
211
|
-
itp buy var_itpay_enterprise_fuzzy_search_cny01 --email buyer@example.com --input company_name=阿里 --json
|
|
224
|
+
itp buy var_itpay_enterprise_fuzzy_search_cny01 --email buyer@example.com --input company_name=阿里 --display agent --no-wait-payment --json
|
|
212
225
|
```
|
|
213
226
|
|
|
214
227
|
For multi-item cart tests:
|
|
@@ -226,6 +239,7 @@ Payment QR rules:
|
|
|
226
239
|
- Otherwise render the ItPay-hosted `qr_png_url` / `preferred_qr_url`.
|
|
227
240
|
- Use `mobile_wallet_url` only as a human mobile fallback.
|
|
228
241
|
- Do not generate your own QR from payment URLs.
|
|
242
|
+
- In agent app clients, send `payment_handoff.markdown` to the human first, then run `payment_handoff.wait_command`.
|
|
229
243
|
- Treat only `payment_intent.verified` as payment success.
|
|
230
244
|
|
|
231
245
|
If the human wants the agent to analyze delivered content, the human must reveal
|
package/bin/itp
CHANGED
|
@@ -93,7 +93,7 @@ async function main() {
|
|
|
93
93
|
output({
|
|
94
94
|
version: VERSION,
|
|
95
95
|
commands: [
|
|
96
|
-
"buy var_pubg_couple_skin_cny20 --sandbox --email buyer@example.com --phone +8613800000000 --json",
|
|
96
|
+
"buy var_pubg_couple_skin_cny20 --sandbox --email buyer@example.com --phone +8613800000000 --display agent --no-wait-payment --json",
|
|
97
97
|
"buy var_pubg_couple_skin_cny20 --sandbox --email buyer@example.com --phone +8613800000000 --no-wait --json",
|
|
98
98
|
"buyer catalog search --query 企业工商 --category business_data_api --provider itpay_enterprise_data --json",
|
|
99
99
|
"buyer catalog get --variant var_pubg_couple_skin_cny20 --json",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"intent": "do both steps through the high-level command",
|
|
71
|
-
"command": "itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --json",
|
|
71
|
+
"command": "itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
|
|
72
72
|
"success_signal": "response.status is waiting_human_auth, waiting_user_payment, payment_verified, or delivery_claimable"
|
|
73
73
|
}
|
|
74
74
|
],
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"commands": [
|
|
18
18
|
{
|
|
19
19
|
"intent": "create payment intent through the high-level buy flow",
|
|
20
|
-
"command": "itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --display agent --json",
|
|
21
|
-
"success_signal": "response.
|
|
20
|
+
"command": "itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
|
|
21
|
+
"success_signal": "response.payment_handoff.markdown and response.payment_handoff.wait_command are present"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"intent": "recover a scanner order-not-found display problem",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"Payment QR display requires both a valid/saved buyer session and payment_intent_id. If payment_intent_id appears before buyer_session is saved, run buyer checkout resume/status instead of showing payment QR.",
|
|
34
34
|
"Do not stop after showing auth_qr unless the human explicitly asks you to pause. Keep waiting/resuming the same checkout so the post-auth payment handoff can continue.",
|
|
35
35
|
"Do not describe auth_qr as payment success or payment proof. Only payment_intent.verified proves payment.",
|
|
36
|
+
"In Codex or Claude Code app clients, the default UX is show-then-wait: show response.payment_handoff.markdown first, then run response.payment_handoff.wait_command.",
|
|
37
|
+
"For Telegram-style message clients, show response.payment_handoff.markdown and stop unless the human asks you to keep waiting or returns with a payment question.",
|
|
36
38
|
"MUST show local_qr_path first when present. Many agent clients do not reliably render remote QR images; local_qr_path is the most reliable desktop/chat display artifact.",
|
|
37
39
|
"If local_qr_path is not present, show qr_png_url or preferred_qr_url as the primary scannable QR. This is an ItPay-hosted human QR image; it may render the native provider payment code for scanner reliability, but the agent must not request or decode the raw provider payload.",
|
|
38
40
|
"Use qr_image_url/SVG only as a fallback when PNG/local rendering is unavailable.",
|
|
@@ -40,7 +42,8 @@
|
|
|
40
42
|
"payment_entry_url is the stable ItPay status/payment page. qr_png_url/local_qr_path is usually the better desktop scanner artifact.",
|
|
41
43
|
"In Alipay sandbox, the provider can briefly show order not found even after precreate succeeded. Tell the human to wait 30-60 seconds and retry the same QR/page. Do not create a new checkout, do not create a new payment intent, and do not repeatedly refresh unless the ItPay page or CLI explicitly asks for recovery.",
|
|
42
44
|
"Opening the payment page must not be treated as paid.",
|
|
43
|
-
"
|
|
45
|
+
"Do not run payment wait before the QR/link has been sent to the human.",
|
|
46
|
+
"After showing QR, run payment_handoff.wait_command or buyer payment wait for the same payment_intent_id."
|
|
44
47
|
],
|
|
45
48
|
"forbidden": [
|
|
46
49
|
"Do not encode payment_entry_url into a new QR yourself when qr_png_url/local_qr_path is available.",
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
29
|
"agent_rules": [
|
|
30
|
+
"Start payment wait only after the QR image/link has already been sent to the human.",
|
|
30
31
|
"Payment wait is a recovery loop around the same payment_intent_id, not a one-shot command.",
|
|
31
32
|
"wait.timeout is a heartbeat, not a payment failure.",
|
|
32
|
-
"
|
|
33
|
+
"In Codex or Claude Code app clients, after showing QR, wait online for a short visible cycle first. If the human still has not paid, explain that the same QR/link remains valid and pause for their reply.",
|
|
34
|
+
"Continue waiting with the same payment_intent_id unless the user explicitly cancels or asks you to pause.",
|
|
33
35
|
"If the network drops, the process is interrupted, or there is no response, rerun payment wait with the same payment_intent_id or run checkout status/resume with the same checkout_id.",
|
|
34
36
|
"If status is unclear, tell the human it is unclear and continue querying the same payment_intent_id/checkout_id. Do not create a new checkout.",
|
|
35
37
|
"Payment success is payment_intent.verified only.",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"intent": "buy a selected variant with the high-level cart-first flow",
|
|
39
|
-
"command": "itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --json",
|
|
40
|
-
"success_signal": "response.status becomes waiting_human_auth
|
|
39
|
+
"command": "itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
|
|
40
|
+
"success_signal": "response.status becomes waiting_human_auth or waiting_user_payment; if waiting_user_payment, response.payment_handoff contains markdown and wait_command"
|
|
41
41
|
}
|
|
42
42
|
],
|
|
43
43
|
"agent_rules": [
|
|
@@ -54,8 +54,9 @@
|
|
|
54
54
|
"After showing auth_qr, keep running/resuming the same checkout unless the human explicitly asks you to pause. Do not stop merely because a QR was displayed.",
|
|
55
55
|
"When a buyer command returns buyer_session.status=buyer_session_saved, the CLI has stored the buyer account session for this agent device. You may continue with checkout/payment or `buyer vault` commands without asking the human for a token.",
|
|
56
56
|
"Vault grants are only for reading approved delivered artifacts. They are not buyer session credentials and do not authorize refunds or account/order management.",
|
|
57
|
-
"Use high-level itp buy when possible
|
|
58
|
-
"
|
|
57
|
+
"Use high-level itp buy when possible, but in agent app clients split payment into two visible steps: first return and show payment_handoff, then run payment_handoff.wait_command.",
|
|
58
|
+
"If response.payment_handoff.markdown is present, send it to the human immediately in the same user-visible message. It includes the QR image and payment link.",
|
|
59
|
+
"Show the returned payment QR exactly as provided before starting payment wait.",
|
|
59
60
|
"Payment truth comes only from payment_intent.verified.",
|
|
60
61
|
"Secure delivery goes to the human first; report redacted delivery status only."
|
|
61
62
|
],
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { coreApi } from "./http.js";
|
|
2
|
+
import { noRecoverableContext } from "./buyer.js";
|
|
3
|
+
import { cliCommand, output, readConfig, readCredentials, readSessionToken, safeErrorMessage } from "./env.js";
|
|
4
|
+
|
|
5
|
+
async function accountOverviewStatus(flags = {}) {
|
|
6
|
+
const config = readConfig();
|
|
7
|
+
const credentials = readCredentials();
|
|
8
|
+
const hasLocalSession = Boolean(readSessionToken(credentials) || flags.access_token);
|
|
9
|
+
const base = {
|
|
10
|
+
schema_version: "itp.agent.v1",
|
|
11
|
+
status: hasLocalSession ? "account_status_unavailable" : "unauthenticated",
|
|
12
|
+
phase: hasLocalSession ? "account_status_unavailable" : "unauthenticated",
|
|
13
|
+
authenticated: false,
|
|
14
|
+
session_verified: !hasLocalSession,
|
|
15
|
+
auth_source: hasLocalSession ? "local_files_unverified" : "local_files",
|
|
16
|
+
account_id: config.account_id || null,
|
|
17
|
+
buyer_account_id: config.account_id || null,
|
|
18
|
+
device_id: config.device_id || null,
|
|
19
|
+
agent_device_id: config.device_id || null,
|
|
20
|
+
linked_providers: [],
|
|
21
|
+
order_count: 0,
|
|
22
|
+
created_at: null,
|
|
23
|
+
device: config.device_id ? { agent_device_id: config.device_id } : null,
|
|
24
|
+
next: hasLocalSession
|
|
25
|
+
? { type: "retry_status", command: cliCommand("status", "--json"), safe_for_agent: true }
|
|
26
|
+
: { type: "start_auth", command: cliCommand("setup", "--plan", "credit-300", "--method", "alipay", "--json"), safe_for_agent: true },
|
|
27
|
+
recoverable_context: noRecoverableContext(),
|
|
28
|
+
agent_next_actions: hasLocalSession
|
|
29
|
+
? ["run_status_refresh", "view_orders", "create_refund_if_needed", "list_agent_read_grants"]
|
|
30
|
+
: ["start_human_auth_if_unauthenticated"],
|
|
31
|
+
secrets: { raw_key_included: false, session_token_included: false }
|
|
32
|
+
};
|
|
33
|
+
if (!hasLocalSession) return base;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const me = await coreApi("/v1/me", { method: "GET" }, flags);
|
|
37
|
+
const authenticated = Boolean(me.authenticated);
|
|
38
|
+
const accountID = me.buyer_account_id || config.account_id || null;
|
|
39
|
+
const deviceID = me.agent_device_id || me.device?.agent_device_id || config.device_id || null;
|
|
40
|
+
return {
|
|
41
|
+
...base,
|
|
42
|
+
status: authenticated ? "idle" : "unauthenticated",
|
|
43
|
+
phase: authenticated ? "idle" : "unauthenticated",
|
|
44
|
+
authenticated,
|
|
45
|
+
session_verified: true,
|
|
46
|
+
auth_source: "core",
|
|
47
|
+
account_id: accountID,
|
|
48
|
+
buyer_account_id: accountID,
|
|
49
|
+
device_id: deviceID,
|
|
50
|
+
agent_device_id: deviceID,
|
|
51
|
+
linked_providers: Array.isArray(me.linked_providers) ? me.linked_providers : [],
|
|
52
|
+
order_count: Number.isFinite(Number(me.order_count)) ? Number(me.order_count) : 0,
|
|
53
|
+
created_at: me.created_at || null,
|
|
54
|
+
device: me.device || (deviceID ? { agent_device_id: deviceID } : null),
|
|
55
|
+
next: authenticated
|
|
56
|
+
? { type: "buyer_ready", command: cliCommand("buyer", "auth", "status", "--json"), safe_for_agent: true }
|
|
57
|
+
: base.next,
|
|
58
|
+
agent_next_actions: authenticated
|
|
59
|
+
? ["search_catalog", "view_orders", "create_refund_if_needed", "list_agent_read_grants"]
|
|
60
|
+
: ["start_human_auth_if_unauthenticated"],
|
|
61
|
+
backend: {
|
|
62
|
+
environment: me.environment || null,
|
|
63
|
+
backend_version: me.backend_version || null
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
...base,
|
|
69
|
+
error: safeErrorMessage(error)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function outputAccountStatus(status, flags = {}) {
|
|
75
|
+
if (flags.json) {
|
|
76
|
+
output(status);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.log(formatStatusText(status));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function formatStatusText(status = {}) {
|
|
83
|
+
if (!status.authenticated) {
|
|
84
|
+
const lines = [
|
|
85
|
+
`Account: ${status.account_id ? `${status.account_id} (unverified)` : "not signed in"}`,
|
|
86
|
+
"Linked: -",
|
|
87
|
+
"Orders: -",
|
|
88
|
+
`Device: ${formatDeviceLine(status.device, status.device_id) || "-"}`
|
|
89
|
+
];
|
|
90
|
+
if (status.error) lines.push(`Error: ${status.error}`);
|
|
91
|
+
return lines.join("\n");
|
|
92
|
+
}
|
|
93
|
+
const linked = Array.isArray(status.linked_providers) && status.linked_providers.length
|
|
94
|
+
? status.linked_providers.join(", ")
|
|
95
|
+
: "-";
|
|
96
|
+
return [
|
|
97
|
+
`Account: ${status.buyer_account_id || status.account_id || "-"}`,
|
|
98
|
+
`Linked: ${linked}`,
|
|
99
|
+
`Orders: ${Number.isFinite(Number(status.order_count)) ? Number(status.order_count) : 0}`,
|
|
100
|
+
`Device: ${formatDeviceLine(status.device, status.device_id) || "-"}`
|
|
101
|
+
].join("\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatDeviceLine(device, fallbackID = "") {
|
|
105
|
+
const displayName = String(device?.display_name || "").trim();
|
|
106
|
+
const deviceID = String(device?.agent_device_id || fallbackID || "").trim();
|
|
107
|
+
const label = displayName || deviceID;
|
|
108
|
+
if (!label) return "";
|
|
109
|
+
const status = String(device?.status || "").trim();
|
|
110
|
+
return status ? `${label} (${status})` : label;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function refreshBuyerSessionForStatus(flags = {}) {
|
|
114
|
+
const config = readConfig();
|
|
115
|
+
const credentials = readCredentials();
|
|
116
|
+
if (!readSessionToken(credentials)) {
|
|
117
|
+
return {
|
|
118
|
+
authenticated: false,
|
|
119
|
+
session_verified: true,
|
|
120
|
+
account_id: config.account_id || null,
|
|
121
|
+
device_id: config.device_id || null,
|
|
122
|
+
auth_source: "core_no_session"
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const status = await coreApi("/v1/me/auth/status", { method: "GET" }, flags);
|
|
127
|
+
return {
|
|
128
|
+
authenticated: true,
|
|
129
|
+
session_verified: true,
|
|
130
|
+
account_id: status.buyer_account_id || config.account_id || null,
|
|
131
|
+
device_id: config.device_id || null,
|
|
132
|
+
account_status: status.account_status || null,
|
|
133
|
+
auth_source: "core"
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return {
|
|
137
|
+
authenticated: false,
|
|
138
|
+
session_verified: true,
|
|
139
|
+
account_id: config.account_id || null,
|
|
140
|
+
device_id: config.device_id || null,
|
|
141
|
+
auth_source: "core",
|
|
142
|
+
error: safeErrorMessage(error)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function nextActionForAuthStatus(auth = {}, flags = {}) {
|
|
148
|
+
if (auth.authenticated && auth.session_verified === false) {
|
|
149
|
+
return { type: "verify_buyer_session", command: cliCommand("status", "--refresh", "--json"), safe_for_agent: true };
|
|
150
|
+
}
|
|
151
|
+
if (auth.authenticated) {
|
|
152
|
+
return { type: "buyer_ready", command: cliCommand("buyer", "auth", "status", "--json"), safe_for_agent: true };
|
|
153
|
+
}
|
|
154
|
+
return { type: "start_auth", command: cliCommand("setup", "--plan", "credit-300", "--method", "alipay", "--json"), safe_for_agent: true };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export { accountOverviewStatus, outputAccountStatus, refreshBuyerSessionForStatus, nextActionForAuthStatus };
|
package/lib/buyer.js
CHANGED
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
writeCredentials, writeSessionCredentials, writeState
|
|
5
5
|
} from "./env.js";
|
|
6
6
|
import { coreApi, coreApiBase } from "./http.js";
|
|
7
|
-
import { renderHumanAction, renderItPayPaymentAction, writeWaitHeartbeat } from "./render-human.js";
|
|
7
|
+
import { renderHumanAction, renderItPayPaymentAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
|
|
8
8
|
|
|
9
9
|
async function buyerBuy(flags) {
|
|
10
|
+
const showThenWait = shouldReturnAfterAgentTextQR(flags);
|
|
10
11
|
const selectionID = flags.selection || flags.variant || flags.catalog_variant_id || flags.item || flags.catalog_item_id;
|
|
11
12
|
if (!selectionID) throw new Error("catalog variant id is required, for example: itp buy var_pubg_couple_skin_cny20 --sandbox --email buyer@example.com --phone +8613800000000 --json");
|
|
12
13
|
const selection = await resolveBuyerCatalogSelection(selectionID, flags);
|
|
@@ -14,7 +15,7 @@ async function buyerBuy(flags) {
|
|
|
14
15
|
let checkout = await createBuyerCheckoutFromCart(cart, selection, flags);
|
|
15
16
|
if (checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth") {
|
|
16
17
|
await renderHumanAction(checkout.human_action, flags);
|
|
17
|
-
if (flags.no_wait || flags.no_wait_auth) {
|
|
18
|
+
if (flags.no_wait || flags.no_wait_auth || showThenWait) {
|
|
18
19
|
output(buyerRunOutput({
|
|
19
20
|
status: "waiting_human_auth",
|
|
20
21
|
selection,
|
|
@@ -41,15 +42,20 @@ async function buyerBuy(flags) {
|
|
|
41
42
|
: await createBuyerPaymentIntent(checkout.checkout_id, flags);
|
|
42
43
|
await renderItPayPaymentAction(intent, flags);
|
|
43
44
|
|
|
44
|
-
if (flags.no_wait || flags.no_wait_payment) {
|
|
45
|
+
if (flags.no_wait || flags.no_wait_payment || showThenWait) {
|
|
45
46
|
output(buyerRunOutput({
|
|
46
47
|
status: "waiting_user_payment",
|
|
47
48
|
selection,
|
|
48
49
|
cart,
|
|
49
50
|
checkout,
|
|
50
51
|
payment_intent: intent,
|
|
52
|
+
payment_handoff: paymentHandoff(intent),
|
|
51
53
|
agent_next_actions: intent.agent_next_actions || ["wait_payment"],
|
|
52
|
-
next: {
|
|
54
|
+
next: {
|
|
55
|
+
command: paymentWaitCommand(intent.payment_intent_id, { short: true }),
|
|
56
|
+
safe_for_agent: true,
|
|
57
|
+
instruction: "Send payment_handoff.markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
58
|
+
}
|
|
53
59
|
}));
|
|
54
60
|
return;
|
|
55
61
|
}
|
|
@@ -312,8 +318,16 @@ async function buyer(command, rest, flags) {
|
|
|
312
318
|
status: intent.status === "verified" ? "payment_verified" : "waiting_user_payment",
|
|
313
319
|
checkout,
|
|
314
320
|
payment_intent: intent,
|
|
321
|
+
payment_handoff: intent.status === "verified" ? undefined : paymentHandoff(intent),
|
|
315
322
|
buyer_session: buyerSessionClaimStatus(claimedSession),
|
|
316
|
-
agent_next_actions: intent.agent_next_actions || checkout.agent_next_actions || ["wait_payment"]
|
|
323
|
+
agent_next_actions: intent.agent_next_actions || checkout.agent_next_actions || ["wait_payment"],
|
|
324
|
+
next: intent.status === "verified"
|
|
325
|
+
? { command: cliCommand("buyer", "checkout", "status", checkout.checkout_id, "--json"), safe_for_agent: true }
|
|
326
|
+
: {
|
|
327
|
+
command: paymentWaitCommand(intent.payment_intent_id, { short: true }),
|
|
328
|
+
safe_for_agent: true,
|
|
329
|
+
instruction: "Send payment_handoff.markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
330
|
+
}
|
|
317
331
|
}));
|
|
318
332
|
return;
|
|
319
333
|
}
|
|
@@ -326,7 +340,18 @@ async function buyer(command, rest, flags) {
|
|
|
326
340
|
}
|
|
327
341
|
const intent = await createBuyerPaymentIntent(checkout.checkout_id, flags);
|
|
328
342
|
await renderItPayPaymentAction(intent, flags);
|
|
329
|
-
output(buyerRunOutput({
|
|
343
|
+
output(buyerRunOutput({
|
|
344
|
+
status: "waiting_user_payment",
|
|
345
|
+
checkout,
|
|
346
|
+
payment_intent: intent,
|
|
347
|
+
payment_handoff: paymentHandoff(intent),
|
|
348
|
+
agent_next_actions: intent.agent_next_actions || ["wait_payment"],
|
|
349
|
+
next: {
|
|
350
|
+
command: paymentWaitCommand(intent.payment_intent_id, { short: true }),
|
|
351
|
+
safe_for_agent: true,
|
|
352
|
+
instruction: "Send payment_handoff.markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
353
|
+
}
|
|
354
|
+
}));
|
|
330
355
|
return;
|
|
331
356
|
}
|
|
332
357
|
output(buyerRunOutput({
|
|
@@ -367,11 +392,16 @@ async function buyer(command, rest, flags) {
|
|
|
367
392
|
output(buyerRunOutput({
|
|
368
393
|
status: refreshed.status === "verified" ? "payment_verified" : "waiting_user_payment",
|
|
369
394
|
payment_intent: refreshed,
|
|
395
|
+
payment_handoff: refreshed.status === "verified" ? undefined : paymentHandoff(refreshed),
|
|
370
396
|
payment_guidance: paymentRecoveryGuidance(refreshed, { event_type: refreshed.status === "verified" ? "payment_intent.verified" : "qr_refreshed" }),
|
|
371
397
|
agent_next_actions: refreshed.agent_next_actions || paymentAgentNextActions(refreshed, { event_type: refreshed.status === "verified" ? "payment_intent.verified" : "qr_refreshed" }),
|
|
372
398
|
next: refreshed.status === "verified"
|
|
373
399
|
? { command: cliCommand("buyer", "checkout", "status", refreshed.checkout_id, "--json"), safe_for_agent: true }
|
|
374
|
-
: {
|
|
400
|
+
: {
|
|
401
|
+
command: paymentWaitCommand(refreshed.payment_intent_id, { short: true }),
|
|
402
|
+
safe_for_agent: true,
|
|
403
|
+
instruction: "Send payment_handoff.markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
404
|
+
}
|
|
375
405
|
}));
|
|
376
406
|
return;
|
|
377
407
|
}
|
|
@@ -997,6 +1027,49 @@ function paymentAgentNextActions(intent = {}, event = {}) {
|
|
|
997
1027
|
return ["show_payment_qr_if_needed", "wait_same_payment_intent"];
|
|
998
1028
|
}
|
|
999
1029
|
|
|
1030
|
+
function paymentHandoff(intent = {}) {
|
|
1031
|
+
const action = intent.human_action || {};
|
|
1032
|
+
const paymentIntentID = intent.payment_intent_id || action.id || "";
|
|
1033
|
+
const entryURL = intent.payment_entry_url || intent.payment_url || action.url || "";
|
|
1034
|
+
const qrPNGURL = action.qr_png_url || intent.qr_png_url || intent.qr?.png_url || "";
|
|
1035
|
+
const preferredQRURL = action.preferred_qr_url || qrPNGURL || action.qr_image_url || intent.qr_image_url || intent.qr?.image_url || "";
|
|
1036
|
+
const localQRPath = action.local_qr_path || intent.local_qr_path || "";
|
|
1037
|
+
const mobileWalletURL = action.mobile_wallet_url || intent.mobile_wallet_url || "";
|
|
1038
|
+
const waitCommand = paymentWaitCommand(paymentIntentID, { short: true });
|
|
1039
|
+
return {
|
|
1040
|
+
type: "payment_qr_handoff",
|
|
1041
|
+
payment_intent_id: paymentIntentID || null,
|
|
1042
|
+
checkout_id: intent.checkout_id || null,
|
|
1043
|
+
primary: localQRPath ? "local_qr_path" : (qrPNGURL ? "qr_png_url" : (preferredQRURL ? "preferred_qr_url" : "payment_entry_url")),
|
|
1044
|
+
local_qr_path: localQRPath || null,
|
|
1045
|
+
qr_png_url: qrPNGURL || null,
|
|
1046
|
+
preferred_qr_url: preferredQRURL || null,
|
|
1047
|
+
payment_entry_url: entryURL || null,
|
|
1048
|
+
mobile_wallet_url: mobileWalletURL || null,
|
|
1049
|
+
markdown: paymentHandoffMarkdown({ localQRPath, qrPNGURL, preferredQRURL, entryURL, mobileWalletURL }),
|
|
1050
|
+
wait_command: waitCommand || null,
|
|
1051
|
+
safe_for_agent: true,
|
|
1052
|
+
instruction: "Show markdown to the human first. Then run wait_command and wait for payment_intent.verified unless the human asked you to pause."
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function paymentHandoffMarkdown({ localQRPath = "", qrPNGURL = "", preferredQRURL = "", entryURL = "", mobileWalletURL = "" } = {}) {
|
|
1057
|
+
const imageURL = localQRPath || qrPNGURL || preferredQRURL;
|
|
1058
|
+
const lines = ["请扫码付款:"];
|
|
1059
|
+
if (imageURL) lines.push(``);
|
|
1060
|
+
if (entryURL) lines.push(`[打开付款页面](${entryURL})`);
|
|
1061
|
+
if (mobileWalletURL) lines.push(`[手机钱包打开](${mobileWalletURL})`);
|
|
1062
|
+
lines.push("我会继续等待 ItPay 返回 payment_intent.verified。");
|
|
1063
|
+
return lines.join("\n\n");
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function paymentWaitCommand(paymentIntentID, { short = false } = {}) {
|
|
1067
|
+
if (!paymentIntentID) return "";
|
|
1068
|
+
return short
|
|
1069
|
+
? cliCommand("buyer", "payment", "wait", paymentIntentID, "--timeout", "60", "--json")
|
|
1070
|
+
: cliCommand("buyer", "payment", "wait", paymentIntentID, "--json");
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1000
1073
|
function paymentRecoveryGuidance(intent = {}, event = {}) {
|
|
1001
1074
|
const paymentIntentID = intent.payment_intent_id || event.payment_intent_id || null;
|
|
1002
1075
|
return {
|
package/lib/render-human.js
CHANGED
|
@@ -8,7 +8,7 @@ import { apiTimeoutMs, cliCommand, commandExists, mergeRun, readRun, readState,
|
|
|
8
8
|
async function renderItPayPaymentAction(intent, flags = {}) {
|
|
9
9
|
const action = intent?.human_action ? { ...intent.human_action } : (intent?.payment_url ? {
|
|
10
10
|
id: intent.payment_intent_id,
|
|
11
|
-
title: "Scan
|
|
11
|
+
title: "Scan payment QR",
|
|
12
12
|
url: intent.payment_url,
|
|
13
13
|
expires_at: intent.qr?.expires_at
|
|
14
14
|
} : null);
|
|
@@ -23,7 +23,7 @@ async function renderItPayPaymentAction(intent, flags = {}) {
|
|
|
23
23
|
if (action && (intent?.qr_image_url || intent?.qr?.image_url)) {
|
|
24
24
|
action.qr_image_url = intent.qr_image_url || intent.qr.image_url;
|
|
25
25
|
action.display_mode = intent.qr?.scan_mode || "itpay_entry_qr";
|
|
26
|
-
action.description = action.description || "Scan the ItPay payment entry QR; ItPay will safely hand off to
|
|
26
|
+
action.description = action.description || "Scan the ItPay payment entry QR; ItPay will safely hand off to the payment provider.";
|
|
27
27
|
}
|
|
28
28
|
const result = await renderHumanAction(action, flags);
|
|
29
29
|
if (intent && action) {
|
|
@@ -39,7 +39,7 @@ function humanActionSummaryLines(action) {
|
|
|
39
39
|
if (!action?.url) return [];
|
|
40
40
|
const lines = [
|
|
41
41
|
"ITP HUMAN ACTION REQUIRED",
|
|
42
|
-
`Title: ${action
|
|
42
|
+
`Title: ${humanActionTitle(action)}`,
|
|
43
43
|
`URL: ${action.url}`
|
|
44
44
|
];
|
|
45
45
|
if (action.local_qr_path) {
|
|
@@ -55,7 +55,7 @@ function humanActionSummaryLines(action) {
|
|
|
55
55
|
lines.push(`Mobile wallet link: ${action.mobile_wallet_url}`);
|
|
56
56
|
}
|
|
57
57
|
if (action.oauth_start_url) {
|
|
58
|
-
lines.push(
|
|
58
|
+
lines.push(`${humanActionProviderLabel(action)} auth fallback: ${action.oauth_start_url}`);
|
|
59
59
|
}
|
|
60
60
|
if (action.fallback_text && !action.fallback_text.includes(action.url)) {
|
|
61
61
|
lines.push(`Fallback: ${action.fallback_text}`);
|
|
@@ -89,7 +89,7 @@ function writeWaitHeartbeat({ kind, idName, idValue, status, action, lastHeartbe
|
|
|
89
89
|
action?.qr_png_url ? `QR PNG: ${action.qr_png_url}` : null,
|
|
90
90
|
action?.qr_image_url ? `QR image: ${action.qr_image_url}` : null,
|
|
91
91
|
action?.mobile_wallet_url ? `Mobile wallet link: ${action.mobile_wallet_url}` : null,
|
|
92
|
-
action?.oauth_start_url ?
|
|
92
|
+
action?.oauth_start_url ? `${humanActionProviderLabel(action)} auth fallback: ${action.oauth_start_url}` : null,
|
|
93
93
|
command ? `Resume command: ${command}` : null
|
|
94
94
|
].filter(Boolean);
|
|
95
95
|
process.stderr.write(`\n${lines.join("\n")}\n\n`);
|
|
@@ -121,7 +121,7 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
121
121
|
}
|
|
122
122
|
return { rendered: false, mode: flags.json ? "json" : mode };
|
|
123
123
|
}
|
|
124
|
-
const host =
|
|
124
|
+
const host = agentHost(flags);
|
|
125
125
|
if (["discord", "telegram", "whatsapp"].includes(host)) {
|
|
126
126
|
return { rendered: false, mode: "chat-json", host };
|
|
127
127
|
}
|
|
@@ -147,9 +147,29 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const renderResult = { rendered: false, mode, outputs: [] };
|
|
150
|
+
const terminalQRAllowed = shouldRenderTerminalQR(host, mode);
|
|
151
|
+
const providerLabel = humanActionProviderLabel(action);
|
|
152
|
+
const actionTitle = humanActionTitle(action);
|
|
150
153
|
writeHumanActionSummary(action);
|
|
154
|
+
|
|
155
|
+
if ((mode === "auto" || mode === "terminal") && terminalQRAllowed) {
|
|
156
|
+
const qr = await QRCode.toString(action.url, {
|
|
157
|
+
type: terminalQRType(flags, host),
|
|
158
|
+
small: true,
|
|
159
|
+
errorCorrectionLevel: "M"
|
|
160
|
+
});
|
|
161
|
+
process.stderr.write(`\n${actionTitle}\n`);
|
|
162
|
+
if (action.description) process.stderr.write(`${action.description}\n`);
|
|
163
|
+
process.stderr.write(`${qr}\n`);
|
|
164
|
+
process.stderr.write(`${providerLabel} action URL: ${action.url}\n`);
|
|
165
|
+
if (action.expires_at) process.stderr.write(`Expires at: ${formatActionTime(action.expires_at)}\n`);
|
|
166
|
+
renderResult.rendered = true;
|
|
167
|
+
renderResult.outputs.push("terminal");
|
|
168
|
+
return renderResult;
|
|
169
|
+
}
|
|
170
|
+
|
|
151
171
|
if ((mode === "auto" || mode === "browser") && shouldOpenBrowser(flags)) {
|
|
152
|
-
if (openBrowser(action.mobile_wallet_url ||
|
|
172
|
+
if (openBrowser(qrImageURL || action.mobile_wallet_url || action.url)) {
|
|
153
173
|
renderResult.rendered = true;
|
|
154
174
|
renderResult.outputs.push("browser");
|
|
155
175
|
}
|
|
@@ -162,7 +182,7 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
162
182
|
await downloadQRImage(qrImageURL, file, flags);
|
|
163
183
|
action.local_qr_path = file;
|
|
164
184
|
action.local_qr_mime = qrMimeType(qrImageURL, action);
|
|
165
|
-
process.stderr.write(
|
|
185
|
+
process.stderr.write(`${providerLabel} QR image: ${file}\n`);
|
|
166
186
|
renderResult.rendered = true;
|
|
167
187
|
renderResult.outputs.push(file);
|
|
168
188
|
if (mode === "file") return renderResult;
|
|
@@ -173,44 +193,42 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
173
193
|
if (localPath) {
|
|
174
194
|
attachAgentLocalQR(action, localPath);
|
|
175
195
|
annotateHumanActionPresentation(action, "");
|
|
176
|
-
process.stderr.write(
|
|
196
|
+
process.stderr.write(`${providerLabel} action QR image: ${localPath}\n`);
|
|
177
197
|
renderResult.rendered = true;
|
|
178
198
|
renderResult.outputs.push(localPath);
|
|
179
199
|
if (mode === "file") return renderResult;
|
|
180
200
|
} else {
|
|
181
|
-
process.stderr.write(`No QR image available. Open action URL: ${action.url}\n`);
|
|
201
|
+
process.stderr.write(`No branded QR image available. Open action URL: ${action.url}\n`);
|
|
182
202
|
}
|
|
183
203
|
}
|
|
184
204
|
}
|
|
185
205
|
|
|
186
206
|
if (qrImageURL) {
|
|
187
|
-
process.stderr.write(
|
|
207
|
+
process.stderr.write(`${providerLabel} QR image URL: ${qrImageURL}\n`);
|
|
188
208
|
if (action.mobile_wallet_url) process.stderr.write(`Mobile wallet link: ${action.mobile_wallet_url}\n`);
|
|
189
209
|
renderResult.outputs.push("preferred_qr_url");
|
|
190
210
|
return renderResult;
|
|
191
211
|
}
|
|
192
212
|
|
|
193
|
-
|
|
194
|
-
const qr = await QRCode.toString(action.url, {
|
|
195
|
-
type: terminalQRType(flags, host),
|
|
196
|
-
small: true,
|
|
197
|
-
errorCorrectionLevel: "M"
|
|
198
|
-
});
|
|
199
|
-
process.stderr.write(`\n${action.title || "Scan with Alipay"}\n`);
|
|
200
|
-
if (action.description) process.stderr.write(`${action.description}\n`);
|
|
201
|
-
process.stderr.write(`${qr}\n`);
|
|
202
|
-
process.stderr.write(`Alipay action URL: ${action.url}\n`);
|
|
203
|
-
if (action.expires_at) process.stderr.write(`Expires at: ${formatActionTime(action.expires_at)}\n`);
|
|
204
|
-
renderResult.rendered = true;
|
|
205
|
-
renderResult.outputs.push("terminal");
|
|
206
|
-
return renderResult;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
process.stderr.write(`${action.fallback_text || `Open Alipay URL: ${action.url}`}\n`);
|
|
213
|
+
process.stderr.write(`${action.fallback_text || `Open ${providerLabel} URL: ${action.url}`}\n`);
|
|
210
214
|
renderResult.outputs.push("url");
|
|
211
215
|
return renderResult;
|
|
212
216
|
}
|
|
213
217
|
|
|
218
|
+
function humanActionTitle(action = {}) {
|
|
219
|
+
if (action.title) return action.title;
|
|
220
|
+
if (action.kind === "auth_qr") return `${humanActionProviderLabel(action)} authentication`;
|
|
221
|
+
return "Scan payment QR";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function humanActionProviderLabel(action = {}) {
|
|
225
|
+
const raw = String(action.provider || action.channel || action.display_mode || "").toLowerCase();
|
|
226
|
+
if (raw.includes("alipay")) return "Alipay";
|
|
227
|
+
if (raw.includes("wechat")) return "WeChat Pay";
|
|
228
|
+
if (raw.includes("fake") || raw.includes("local")) return "ItPay local";
|
|
229
|
+
return "Alipay or WeChat Pay";
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
function preferredHumanActionQRURL(action) {
|
|
215
233
|
return action?.qr_png_url ||
|
|
216
234
|
humanActionPresentationURL(action, "qr_png_url") ||
|
|
@@ -338,8 +356,8 @@ function formatActionTime(value) {
|
|
|
338
356
|
|
|
339
357
|
function shouldUseAgentTextQR(flags = {}) {
|
|
340
358
|
const mode = String(flags.display || process.env.ITP_DISPLAY || "").toLowerCase() || "auto";
|
|
341
|
-
const host =
|
|
342
|
-
return mode === "chat" || mode === "agent" || (
|
|
359
|
+
const host = agentHost(flags);
|
|
360
|
+
return mode === "chat" || mode === "agent" || (agentTextQRHosts().has(host) && mode === "auto");
|
|
343
361
|
}
|
|
344
362
|
|
|
345
363
|
function shouldReturnAfterAgentTextQR(flags = {}) {
|
|
@@ -347,6 +365,18 @@ function shouldReturnAfterAgentTextQR(flags = {}) {
|
|
|
347
365
|
return shouldUseAgentTextQR(flags);
|
|
348
366
|
}
|
|
349
367
|
|
|
368
|
+
function agentHost(flags = {}) {
|
|
369
|
+
const explicit = String(process.env.ITP_HOST || flags.host || "").toLowerCase();
|
|
370
|
+
if (explicit) return explicit;
|
|
371
|
+
if (process.env.CODEX_THREAD_ID || process.env.CODEX_SHELL || process.env.CODEX_CI) return "codex";
|
|
372
|
+
if (process.env.CLAUDECODE || process.env.CLAUDE_CODE || process.env.CLAUDECODE_SESSION_ID) return "claude-code";
|
|
373
|
+
return "";
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function agentTextQRHosts() {
|
|
377
|
+
return new Set(["codex", "codex-cli", "claude", "claude-code", "gemini", "gemini-cli"]);
|
|
378
|
+
}
|
|
379
|
+
|
|
350
380
|
function attachAgentQRImage(action, qrImageURL, localPath = "") {
|
|
351
381
|
if (!action || !qrImageURL) return action;
|
|
352
382
|
action.preferred_qr_url = qrImageURL;
|
|
@@ -416,16 +446,22 @@ function persistHumanAction(action, flags = {}) {
|
|
|
416
446
|
writeRun(mergeRun(run, { human_action: action }));
|
|
417
447
|
}
|
|
418
448
|
|
|
419
|
-
function shouldRenderTerminalQR(host) {
|
|
449
|
+
function shouldRenderTerminalQR(host, mode = "auto") {
|
|
450
|
+
if (["terminal", "tty"].includes(mode)) return true;
|
|
451
|
+
if (mode && !["auto", ""].includes(mode)) return false;
|
|
420
452
|
if (process.stderr.isTTY) return true;
|
|
421
|
-
return
|
|
453
|
+
return terminalQRHostAliases().has(String(host || "").toLowerCase());
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function terminalQRHostAliases() {
|
|
457
|
+
return new Set(["terminal", "tty", "bash", "zsh", "sh", "shell", "iterm", "iterm2"]);
|
|
422
458
|
}
|
|
423
459
|
|
|
424
460
|
function terminalQRType(flags = {}, host = "") {
|
|
425
461
|
const requested = String(flags.qr_format || process.env.ITP_QR_FORMAT || "").toLowerCase();
|
|
426
462
|
if (requested === "unicode" || requested === "utf8") return "utf8";
|
|
427
463
|
if (requested === "ansi" || requested === "terminal") return "terminal";
|
|
428
|
-
if (
|
|
464
|
+
if (terminalQRHostAliases().has(String(host || "").toLowerCase())) return "utf8";
|
|
429
465
|
return "terminal";
|
|
430
466
|
}
|
|
431
467
|
|
package/lib/runtime.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
recoverableBuyerContextForStatus, recoverableIntentCheckGuidance
|
|
8
8
|
} from "./buyer.js";
|
|
9
9
|
import { renderHumanAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
|
|
10
|
+
import { accountOverviewStatus, nextActionForAuthStatus, outputAccountStatus, refreshBuyerSessionForStatus } from "./account-status.js";
|
|
10
11
|
import {
|
|
11
12
|
CREDENTIALS_PATH, CONFIG_DIR, RUNS_DIR, VERSION, apiBase, apiTimeoutMs, appendURLQuery, booleanFlag, cliCommand, commandExists,
|
|
12
13
|
cryptoRandom, csvValues, currentExecutable, deleteGrantCredential, deleteSessionCredential, detectNativeCredentialStore,
|
|
@@ -131,14 +132,14 @@ async function setup(flags) {
|
|
|
131
132
|
grant_id: checkout.grant_id || run.grant?.grant_id || null
|
|
132
133
|
},
|
|
133
134
|
human_action: checkout.human_action || null,
|
|
134
|
-
safe_summary: checkout.grant_id ? "Payment verified and grant is ready." : "Waiting for Alipay payment scan."
|
|
135
|
+
safe_summary: checkout.grant_id ? "Payment verified and grant is ready." : "Waiting for Alipay or WeChat Pay payment scan."
|
|
135
136
|
});
|
|
136
137
|
writeRun(run);
|
|
137
138
|
|
|
138
139
|
if (checkout.human_action) {
|
|
139
140
|
await renderHumanAction(checkout.human_action, setupFlags);
|
|
140
141
|
} else if (checkout.payment?.cashier_url) {
|
|
141
|
-
process.stderr.write(`Open Alipay payment URL: ${checkout.payment.cashier_url}\n`);
|
|
142
|
+
process.stderr.write(`Open Alipay or WeChat Pay payment URL: ${checkout.payment.cashier_url}\n`);
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
if (!checkout.grant_id && (setupFlags.no_wait_payment || setupFlags.no_wait)) {
|
|
@@ -263,40 +264,7 @@ async function agentStatus(flags) {
|
|
|
263
264
|
const runId = flags.run_id || readState().current_run_id;
|
|
264
265
|
const run = readRun(runId);
|
|
265
266
|
if (!run) {
|
|
266
|
-
|
|
267
|
-
const credentials = readCredentials();
|
|
268
|
-
const hasLocalSession = Boolean(readSessionToken(credentials));
|
|
269
|
-
const auth = flags.refresh
|
|
270
|
-
? await refreshBuyerSessionForStatus(flags)
|
|
271
|
-
: {
|
|
272
|
-
authenticated: hasLocalSession,
|
|
273
|
-
session_verified: false,
|
|
274
|
-
account_id: config.account_id || null,
|
|
275
|
-
device_id: config.device_id || null,
|
|
276
|
-
auth_source: hasLocalSession ? "local_files_unverified" : "local_files"
|
|
277
|
-
};
|
|
278
|
-
output({
|
|
279
|
-
schema_version: "itp.agent.v1",
|
|
280
|
-
status: auth.authenticated
|
|
281
|
-
? (auth.session_verified === false ? "local_session_unverified" : "idle")
|
|
282
|
-
: "unauthenticated",
|
|
283
|
-
phase: auth.authenticated ? "idle" : "unauthenticated",
|
|
284
|
-
authenticated: Boolean(auth.authenticated),
|
|
285
|
-
session_verified: auth.session_verified !== false,
|
|
286
|
-
auth_source: auth.auth_source,
|
|
287
|
-
account_id: auth.account_id || null,
|
|
288
|
-
device_id: auth.device_id || null,
|
|
289
|
-
next: nextActionForAuthStatus(auth, flags),
|
|
290
|
-
recoverable_context: noRecoverableContext(),
|
|
291
|
-
agent_next_actions: auth.authenticated && auth.session_verified !== false
|
|
292
|
-
? ["search_catalog", "view_orders", "create_refund_if_needed", "list_agent_read_grants"]
|
|
293
|
-
: ["run_status_refresh", "start_human_auth_if_unauthenticated"],
|
|
294
|
-
note: auth.authenticated && auth.session_verified === false
|
|
295
|
-
? "Local buyer session exists but the server has not verified it. Run the next.command before order/refund/account operations."
|
|
296
|
-
: undefined,
|
|
297
|
-
error: auth.error || undefined,
|
|
298
|
-
secrets: { raw_key_included: false, session_token_included: false }
|
|
299
|
-
});
|
|
267
|
+
outputAccountStatus(await accountOverviewStatus(flags), flags);
|
|
300
268
|
return;
|
|
301
269
|
}
|
|
302
270
|
const refreshed = flags.refresh ? await refreshRun(run, flags) : run;
|
|
@@ -306,50 +274,6 @@ async function agentStatus(flags) {
|
|
|
306
274
|
}));
|
|
307
275
|
}
|
|
308
276
|
|
|
309
|
-
async function refreshBuyerSessionForStatus(flags = {}) {
|
|
310
|
-
const config = readConfig();
|
|
311
|
-
const credentials = readCredentials();
|
|
312
|
-
if (!readSessionToken(credentials)) {
|
|
313
|
-
return {
|
|
314
|
-
authenticated: false,
|
|
315
|
-
session_verified: true,
|
|
316
|
-
account_id: config.account_id || null,
|
|
317
|
-
device_id: config.device_id || null,
|
|
318
|
-
auth_source: "core_no_session"
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
try {
|
|
322
|
-
const status = await coreApi("/v1/me/auth/status", { method: "GET" }, flags);
|
|
323
|
-
return {
|
|
324
|
-
authenticated: true,
|
|
325
|
-
session_verified: true,
|
|
326
|
-
account_id: status.buyer_account_id || config.account_id || null,
|
|
327
|
-
device_id: config.device_id || null,
|
|
328
|
-
account_status: status.account_status || null,
|
|
329
|
-
auth_source: "core"
|
|
330
|
-
};
|
|
331
|
-
} catch (error) {
|
|
332
|
-
return {
|
|
333
|
-
authenticated: false,
|
|
334
|
-
session_verified: true,
|
|
335
|
-
account_id: config.account_id || null,
|
|
336
|
-
device_id: config.device_id || null,
|
|
337
|
-
auth_source: "core",
|
|
338
|
-
error: safeErrorMessage(error)
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function nextActionForAuthStatus(auth = {}, flags = {}) {
|
|
344
|
-
if (auth.authenticated && auth.session_verified === false) {
|
|
345
|
-
return { type: "verify_buyer_session", command: cliCommand("status", "--refresh", "--json"), safe_for_agent: true };
|
|
346
|
-
}
|
|
347
|
-
if (auth.authenticated) {
|
|
348
|
-
return { type: "buyer_ready", command: cliCommand("buyer", "auth", "status", "--json"), safe_for_agent: true };
|
|
349
|
-
}
|
|
350
|
-
return { type: "start_auth", command: cliCommand("setup", "--plan", "credit-300", "--method", "alipay", "--json"), safe_for_agent: true };
|
|
351
|
-
}
|
|
352
|
-
|
|
353
277
|
async function resume(flags) {
|
|
354
278
|
const runId = flags.run_id || readState().current_run_id;
|
|
355
279
|
const run = readRun(runId);
|
|
@@ -761,7 +685,7 @@ async function createCheckoutResult(flags) {
|
|
|
761
685
|
grant_id: response.grant_id || currentRun?.grant?.grant_id || null
|
|
762
686
|
},
|
|
763
687
|
human_action: response.human_action || null,
|
|
764
|
-
safe_summary: response.grant_id ? "Payment verified and grant is ready." : "Waiting for Alipay payment scan."
|
|
688
|
+
safe_summary: response.grant_id ? "Payment verified and grant is ready." : "Waiting for Alipay or WeChat Pay payment scan."
|
|
765
689
|
}, flags);
|
|
766
690
|
return response;
|
|
767
691
|
}
|
|
@@ -827,7 +751,7 @@ async function paymentWaitResult(checkoutId, flags) {
|
|
|
827
751
|
}
|
|
828
752
|
}
|
|
829
753
|
lastHeartbeatAt = writeWaitHeartbeat({
|
|
830
|
-
kind: "Alipay payment",
|
|
754
|
+
kind: "Alipay or WeChat Pay payment",
|
|
831
755
|
idName: "checkout_id",
|
|
832
756
|
idValue: checkoutId,
|
|
833
757
|
status: response.status || "waiting",
|
|
@@ -1376,7 +1300,7 @@ async function refreshRun(run, flags = {}) {
|
|
|
1376
1300
|
payment: { provider: next.payment_method, status: checkout.status },
|
|
1377
1301
|
grant: { ...(next.grant || {}), grant_id: checkout.grant_id || next.grant?.grant_id || null },
|
|
1378
1302
|
human_action: checkout.human_action || null,
|
|
1379
|
-
safe_summary: checkout.grant_id ? "Payment verified and grant is ready." : checkout.status === "waiting_user_payment" ? "Waiting for Alipay payment scan." : `Checkout status: ${checkout.status}`
|
|
1303
|
+
safe_summary: checkout.grant_id ? "Payment verified and grant is ready." : checkout.status === "waiting_user_payment" ? "Waiting for Alipay or WeChat Pay payment scan." : `Checkout status: ${checkout.status}`
|
|
1380
1304
|
});
|
|
1381
1305
|
} catch (error) {
|
|
1382
1306
|
next = mergeRun(next, { last_error: safeErrorMessage(error), safe_summary: "Could not refresh checkout status." });
|
|
@@ -1498,7 +1422,7 @@ function appendPassthroughFlag(args, flags, key, flagName = key) {
|
|
|
1498
1422
|
|
|
1499
1423
|
function safeUserMessageForRun(run) {
|
|
1500
1424
|
if (run.phase === "waiting_human_auth") return "Please scan the Alipay authentication QR. I will continue automatically after approval.";
|
|
1501
|
-
if (run.phase === "waiting_human_payment") return "Please scan the Alipay payment QR. I will continue automatically after payment is verified.";
|
|
1425
|
+
if (run.phase === "waiting_human_payment") return "Please scan the Alipay or WeChat Pay payment QR. I will continue automatically after payment is verified.";
|
|
1502
1426
|
if (run.grant?.installed) return "Payment verified. The API credential is stored locally.";
|
|
1503
1427
|
return run.safe_summary || "ITPay setup is in progress.";
|
|
1504
1428
|
}
|
package/package.json
CHANGED
|
@@ -59,7 +59,7 @@ read this skill
|
|
|
59
59
|
The high-level command can wrap this flow:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --json
|
|
62
|
+
itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
For step-by-step testing:
|
|
@@ -93,7 +93,7 @@ itp buyer cart create --variant var_itpay_enterprise_fuzzy_search_cny01 --input
|
|
|
93
93
|
itp buyer cart show <cart_id> --json
|
|
94
94
|
itp buyer cart add <cart_id> --variant var_itpay_enterprise_fuzzy_search_cny01 --input company_name=美团 --json
|
|
95
95
|
itp buyer cart create --variant var_itpay_enterprise_precise_lookup_cny05 --input company_name_or_credit_no=北京京东世纪贸易有限公司 --json
|
|
96
|
-
itp buy var_itpay_enterprise_fuzzy_search_cny01 --sandbox --email <buyer_email> --input company_name=京东 --json
|
|
96
|
+
itp buy var_itpay_enterprise_fuzzy_search_cny01 --sandbox --email <buyer_email> --input company_name=京东 --display agent --no-wait-payment --json
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
For cart edits, always read the server cart first with `buyer cart show`.
|
|
@@ -206,13 +206,16 @@ Each docs page includes `next_docs`. Follow those links as the state changes.
|
|
|
206
206
|
For payment creation in an agent/chat client, prefer:
|
|
207
207
|
|
|
208
208
|
```bash
|
|
209
|
-
itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --display agent --json
|
|
209
|
+
itp buy <variant_id> --sandbox --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
This keeps JSON output machine-readable while allowing the CLI to prepare a
|
|
213
|
-
local QR image path for clients that cannot render remote SVG reliably.
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
local QR image path for clients that cannot render remote SVG reliably. In
|
|
214
|
+
Codex or Claude Code app clients, prefer `--no-wait-payment`: send
|
|
215
|
+
`payment_handoff.markdown` to the human first, then run
|
|
216
|
+
`payment_handoff.wait_command`. If the human is on mobile, present
|
|
217
|
+
`mobile_wallet_url` as a clickable human-only fallback; do not convert it into a
|
|
218
|
+
QR.
|
|
216
219
|
|
|
217
220
|
For first-purchase auth, treat the returned ItPay authorization entry as a
|
|
218
221
|
single human orchestration entry. It may open Alipay login/registration first
|